/*!
\copyright  Copyright (c) 2022-2023 Qualcomm Technologies International, Ltd.\n
            All Rights Reserved.\n
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\file
\brief      Volume observer that syncs between LE Audio and USB
*/

#ifdef INCLUDE_SOURCE_APP_LE_AUDIO
#include "usb_dongle_logging.h"
#include "usb_dongle_volume_observer_le.h"
#include "audio_sources.h"
#include <usb_device.h>
#include <usb_audio.h>
#include <volume_utils.h>
#include <volume_service.h>
#include <volume_messages.h>
#include <telephony_messages.h>
#include "le_audio_client.h"

#define USB_DONGLE_VO_USB_VOLUME_CHANGE_DELAY_MS    20  /*!< delay between sending USB Volume inc/dec commands */
#define USB_DONGLE_VO_LE_AUDIO_VOLUME_CHANGE_DELAY_MS  200 /*!< delay between sending LE Audio Volume updates commands */

/* USB volume (in percentage) below which the volume down HID command is not allowed to send
   if the target volume is non-zero for windows host */
#define USB_DONGLE_VO_LE_USB_VOLUME_CUTOFF_FOR_VOLUME_DOWN     7

/* if the USB host becomes unresponsive, we need to stop sending volume messages */
#define USB_VOLUME_CHANGE_COUNT_MAX 20

/* Burst sent to classify Host OS */
#define BURST_SIZE 4

/* As per Volume Control Service document, Table 3.8*/
#define LE_AUDIO_VOLUME_MIN_STEPS      0
#define LE_AUDIO_VOLUME_MAX_STEPS      255
#define LE_AUDIO_VOLUME_NUM_STEPS      ((LE_AUDIO_VOLUME_MAX_STEPS - LE_AUDIO_VOLUME_MIN_STEPS) + 1)

static void usbDongle_LeaVolumeNotifyAudioRoutingChange(audio_source_t source, source_routing_change_t change);
static void usbDongle_LeaVolumeNotifyVolumeChange(audio_source_t source, event_origin_t origin, volume_t volume);
static void usbDongle_LeaVolumeNotifyVoiceVolumeChange(voice_source_t source, event_origin_t origin, volume_t volume);
static void usbDongle_LeaVolumeNotifyVoiceMuteChange(voice_source_t source, event_origin_t origin, bool mute_state);
static void usbDongle_LeaVolumeHandleUpdateUsbVolume(void);

enum vo_internal_message_ids
{
    VO_INTERNAL_UPDATE_USB_VOLUME,      /*!< Message used to create a delay between each USB volume inc/dec */
    VO_INTERNAL_UPDATE_LE_AUDIO_VOLUME     /*!< Message used to create a delay between each LE Audio absolute volume */
};

typedef enum vo_internal_state
{
    VO_INTERNAL_STATE_IDLE,                 /*!< No volume changes in this state */
    VO_INTERNAL_STATE_USB_VOLUME_SYNC,      /*!< Syncing the USB volume */
    VO_INTERNAL_STATE_SINK_VOLUME_SYNC      /*!< Syncing the Sink volume */
} vo_internal_state_t;

typedef enum host_os_type
{
    HOST_OS_TYPE_WINDOWS_MAC,        /*!< Host OS is Windows or MacOS */
    HOST_OS_TYPE_LINUX_ANDROID,      /*!< Host OS is Linux or Android */
    HOST_OS_TYPE_UNIDENTIFIED        /*!< Host OS has not been identified */
} host_os_type_t;

/* data for handling USB volume updates */
typedef struct {
    uint8 count;            /*!< volume changes sent without a volume update from the USB host */
    int16 target;           /*!< target volume we are aiming for */
    bool up;                /*!< TRUE = increase volume, FALSE = decrement volume */
} usb_volume_t;

/* data for handling LE Audio volume updates */
typedef struct {
    uint8 target;           /*!< value to set on LE Audio */
} le_audio_volume_t;

typedef struct {
    TaskData task;

    bool usb_connected; 		/*!< USB is connected */
    bool le_audio_connected;
    bool le_audio_vbc_active;
    gatt_cid_t le_audio_cid;
    ServiceHandle le_audio_group_handle;

    /* data for handling USB volume updates */
    usb_volume_t usb_volume;
    /* data for handling LE Audio volume updates */
    le_audio_volume_t le_audio_volume;

    vo_internal_state_t state;
    bool mic_muted;

    volume_t old_volume; /*!< Previous USB volume */
    host_os_type_t host_os; /*!< Holds the Host OS type */

} usb_dongle_lea_volume_observer_data_t;

static usb_dongle_lea_volume_observer_data_t usb_dongle_lea_volume_observer_data;
#define GetTaskData()   (&usb_dongle_lea_volume_observer_data)
#define GetTask()       (&(GetTaskData()->task))

/* The volume range of LE Audio */
const volume_config_t le_audio_config = {
    .range = {
        .max = LE_AUDIO_VOLUME_MAX_STEPS,
        .min = LE_AUDIO_VOLUME_MIN_STEPS
    },
    .number_of_steps = LE_AUDIO_VOLUME_NUM_STEPS
};

/* The volume range of USB */
const volume_config_t usb_config = {
    .range = {
        .max = USB_AUDIO_VOLUME_MAX_STEPS,
        .min = USB_AUDIO_VOLUME_MIN_STEPS
    },
    .number_of_steps = USB_AUDIO_VOLUME_NUM_STEPS
};

static const audio_source_observer_interface_t usb_dongle_observer_interface =
{
    .OnVolumeChange = usbDongle_LeaVolumeNotifyVolumeChange,
    .OnAudioRoutingChange = usbDongle_LeaVolumeNotifyAudioRoutingChange
};

static const voice_source_observer_interface_t usb_dongle_voice_observer_interface =
{
    .OnVolumeChange = usbDongle_LeaVolumeNotifyVoiceVolumeChange,
    .OnMuteChange = usbDongle_LeaVolumeNotifyVoiceMuteChange
};

typedef enum
{
    request_type_host_to_sink,
    request_type_sink_to_host
} request_type_t;

/* send the LE Audio volume update */
static void usbDongle_LeaVolumeHandleUpdateLeAudioVolume(void)
{
    DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeHandleUpdateLeAudioVolume, volume %d", GetTaskData()->le_audio_volume.target);

    LeAudioClient_SetAbsoluteVolume(GetTaskData()->le_audio_group_handle, GetTaskData()->le_audio_volume.target);
}

/*
 * Throttle the rate at which we send LE Audio absolute volume updates
 * This is done to avoid a memory exhausation situation if the PC host sends us too many volume update messages
 */
static void usbDongle_LeaVolumeQueueLeAudioVolumeUpdate(uint8  volume)
{
    int32 dummy;

    DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeQueueLeAudioVolumeUpdate");

    GetTaskData()->le_audio_volume.target = volume;
    if(!MessagePendingFirst(GetTask(), VO_INTERNAL_UPDATE_LE_AUDIO_VOLUME, &dummy))
    {
        DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeQueueLeAudioVolumeUpdate, queuing LEA volume update");
        MessageSendLater(GetTask(), VO_INTERNAL_UPDATE_LE_AUDIO_VOLUME, NULL, USB_DONGLE_VO_LE_AUDIO_VOLUME_CHANGE_DELAY_MS);
    }
    else
    {
        DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeQueueLeAudioVolumeUpdate, not queuing, volume update already pending");
    }
}

static void usbDongle_LeaVolumeUpdateSinkVolume(volume_t volume)
{
    if(GetTaskData()->le_audio_connected)
    {
        volume_t old_volume = AudioSources_GetVolume(audio_source_le_audio_unicast);
        int16 new_volume;
        new_volume = VolumeUtils_ConvertToVolumeConfig(volume, le_audio_config);
        DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeUpdateSinkVolume, USB %d, old %d, new %d",
                          volume.value, old_volume.value, new_volume);
        usbDongle_LeaVolumeQueueLeAudioVolumeUpdate(new_volume);
    }
    else
    {
        DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeUpdateSinkVolume, no LE Audio connection");
    }
}

/*
 * Find if we need to increment or decrement the USB volume
 */
static void usbDongle_LeaVolumeUpdateUsbVolumeChangeDirection(volume_t usb_volume, int16 new_usb_volume)
{
    /* USB volume is already at the target, no change required */
    if(new_usb_volume == usb_volume.value)
    {
        DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeUpdateUsbVolumeChangeDirection, no change" );
        GetTaskData()->state = VO_INTERNAL_STATE_SINK_VOLUME_SYNC;
    }
    else
    {
        /* calculate the change, up or down */
        GetTaskData()->usb_volume.target = new_usb_volume;
        if(new_usb_volume > usb_volume.value)
        {
            DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeUpdateUsbVolumeChangeDirection, volume up" );
            GetTaskData()->usb_volume.up = TRUE;
        }
        else
        {
            DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeUpdateUsbVolumeChangeDirection, volume down" );
            GetTaskData()->usb_volume.up = FALSE;
        }
        /* send HID volume messages */
        usbDongle_LeaVolumeHandleUpdateUsbVolume();
    }
}

/*
 * Different strategies to update USB volume target depending on the host OS.
 */
static void usbDongle_LeaVolumeUpdateUsbVolumeTarget(volume_t le_audio_volume)
{
    volume_t usb_volume = AudioSources_GetVolume(audio_source_usb);
    int16 new_usb_volume = VolumeUtils_ConvertToVolumeConfig(le_audio_volume, usb_config);

    DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeUpdateUsbVolumeTarget, LE Audio %d, old USB %d, new USB %d",
                      le_audio_volume.value,usb_volume.value,new_usb_volume );
    MessageCancelFirst(GetTask(), VO_INTERNAL_UPDATE_USB_VOLUME);

    switch(GetTaskData()->host_os)
    {
        /* Current USB volume compared to target USB volume. */
        case HOST_OS_TYPE_WINDOWS_MAC:
            usbDongle_LeaVolumeUpdateUsbVolumeChangeDirection(usb_volume, new_usb_volume);
            break;

        /* Previous target USB volume compared to new target USB volume.
         * Because we do not know current USB volume. */
        case HOST_OS_TYPE_LINUX_ANDROID:
            usbDongle_LeaVolumeUpdateUsbVolumeChangeDirection(GetTaskData()->old_volume, new_usb_volume);
            GetTaskData()->old_volume.value = new_usb_volume;
            break;

        /* For the first volume request, current USB volume is compared to target USB volume.
         * And the LE Audio volume is set.*/
        case HOST_OS_TYPE_UNIDENTIFIED:
            usbDongle_LeaVolumeUpdateUsbVolumeChangeDirection(usb_volume, new_usb_volume);
            break;

        default:
            break;
    }
}

static void usbDongle_LeaVolumeStateUsbVolumeSync(request_type_t request_type, volume_t volume)
{
    switch(request_type)
    {
        /* Host has sent us a USB volume update, check if we've met the target */
        case request_type_host_to_sink:
            GetTaskData()->usb_volume.count = 0;
            GetTaskData()->host_os = HOST_OS_TYPE_WINDOWS_MAC;
            DEBUG_LOG_INFO("usbDongle_LeaVolumeStateUsbVolumeSync, likely host OS: Windows or Mac");
            if (GetTaskData()->usb_volume.up )
            {
                if ( volume.value >= GetTaskData()->usb_volume.target )
                {
                    DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeStateUsbVolumeSync,volume target met");
                    GetTaskData()->state = VO_INTERNAL_STATE_SINK_VOLUME_SYNC;
                    MessageCancelFirst(GetTask(), VO_INTERNAL_UPDATE_USB_VOLUME);
                }
            }
            else
            {
                if ( volume.value <= GetTaskData()->usb_volume.target )
                {
                    DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeStateUsbVolumeSync, volume target met");
                    GetTaskData()->state = VO_INTERNAL_STATE_SINK_VOLUME_SYNC;
                    MessageCancelFirst(GetTask(), VO_INTERNAL_UPDATE_USB_VOLUME);
                }
            }
            break;

        /* Sink is requesting a volume update while we are syncing the USB volume, update target */
        case request_type_sink_to_host:
            usbDongle_LeaVolumeUpdateUsbVolumeTarget(volume);
            break;

        default:
            DEBUG_LOG_WARN("usbDongle_LeaVolumeStateUsbVolumeSync,invalid source");
            break;
    }
}

static void usbDongle_LeaVolumeStateSinkVolumeSync(request_type_t request_type, volume_t volume)
{
    switch(request_type)
    {
        /* Host volume changed, update the Sink volume */
        case request_type_host_to_sink:
            usbDongle_LeaVolumeUpdateSinkVolume(volume);
            break;

        /* Sink volume changed, update the USB volume, change state */
        case request_type_sink_to_host:
            GetTaskData()->state = VO_INTERNAL_STATE_USB_VOLUME_SYNC;
            GetTaskData()->usb_volume.count = 0;
            usbDongle_LeaVolumeUpdateUsbVolumeTarget(volume);
            break;

        default:
            DEBUG_LOG_WARN("usbDongle_LeaVolumeStateSinkVolumeSync, invalid source");
            break;
    }
}

static void usbDongle_LeaVolumeNotifyAudioRoutingChange(audio_source_t source, source_routing_change_t change)
{
    DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeNotifyAudioRoutingChange, enum:audio_source_t:%d, "
                      "enum:source_routing_change_t:%d", source, change);
}

static void usbDongle_LeaVolumeHandleNotifyVolumeChange(request_type_t request_type, volume_t volume)
{
    switch(GetTaskData()->state)
    {
        case VO_INTERNAL_STATE_SINK_VOLUME_SYNC:
            usbDongle_LeaVolumeStateSinkVolumeSync(request_type, volume);
            break;

        case VO_INTERNAL_STATE_USB_VOLUME_SYNC:
            usbDongle_LeaVolumeStateUsbVolumeSync(request_type, volume);
            break;

        case VO_INTERNAL_STATE_IDLE:
            DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeHandleNotifyVolumeChange, IDLE no change");
            break;

        default:
            break;
    }
}

static void usbDongle_LeaVolumeNotifyVolumeChange(audio_source_t source, event_origin_t origin, volume_t volume)
{
    DEBUG_LOG_INFO("usbDongle_LeaVolumeNotifyVolumeChange, enum:audio_source_t:%d, "
                   "enum:event_origin_t:%d, volume %d", source, origin, volume.value);

    if ( origin == event_origin_external )
    {
        usbDongle_LeaVolumeHandleNotifyVolumeChange(source == audio_source_usb ?
                                                     request_type_host_to_sink :
                                                     request_type_sink_to_host, volume);
    }
    else
    {
        DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeNotifyVolumeChange, ignoring origin enum:event_origin_t:%d", origin);
    }
}

static void usbDongle_LeaVolumeNotifyVoiceVolumeChange(voice_source_t source, event_origin_t origin, volume_t volume)
{
    DEBUG_LOG_INFO("usbDongle_LeaVolumeNotifyVoiceVolumeChange, enum:voice_source_t:%d, "
                   "enum:event_origin_t:%d, volume %d", source, origin, volume.value);

    if ( origin == event_origin_external )
    {
        usbDongle_LeaVolumeHandleNotifyVolumeChange(source == voice_source_usb ?
                                                     request_type_host_to_sink :
                                                     request_type_sink_to_host, volume);
    }
    else
    {
        DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeNotifyVoiceVolumeChange, ignoring origin enum:event_origin_t:%d", origin);
    }
}

static void usbDongle_LeaVolumeNotifyVoiceMuteChange(voice_source_t source, event_origin_t origin, bool mute_state)
{
    DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeNotifyVoiceMuteChange, enum:voice_source_t:%d,"
                      " enum:event_origin_t:%d, mute %d", source, origin, mute_state);
}

/*
 * Send a HID Volume Up/Down message to the host
 */
static void usbDongle_LeaVolumeSendUsbVolumeMessage(void)
{
    if(GetTaskData()->usb_volume.up)
    {
        DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeSendUsbVolumeMessage, up");
        Volume_SendAudioSourceVolumeIncrementRequest(audio_source_usb, event_origin_local);
    }
    else
    {
        DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeSendUsbVolumeMessage, down");
        Volume_SendAudioSourceVolumeDecrementRequest(audio_source_usb, event_origin_local);
    }
}

/* Returns TRUE if volume up/down command can be send to the host, FALSE otherwise */
static bool usbDongle_LeaCheckIfUsbVolumeUpDownCanBeApplied(int16 new_usb_volume)
{
    int current_usb_volume_in_percent;
    bool status = TRUE;

    if (!GetTaskData()->usb_volume.up)
    {
        current_usb_volume_in_percent = VolumeUtils_GetVolumeInPercent(AudioSources_GetVolume(audio_source_usb));

        /* Against windows host, it is seen that sending volume down command when volume is lesser than
           USB_DONGLE_VO_LE_USB_VOLUME_CUTOFF_FOR_VOLUME_DOWN sometimes results in setting the volume
           directly to zero in windows sound bar UI along with a muted symbol. However the sink device
           volume level might still remain as non-zero. So to avoid this, here we make sure the volume
           down command is not sent unless the target usb volume is really zero */
        if (current_usb_volume_in_percent <= USB_DONGLE_VO_LE_USB_VOLUME_CUTOFF_FOR_VOLUME_DOWN &&
            new_usb_volume > 0)
        {
            status = FALSE;
        }
    }

    return status;
}

/*! \brief Send HID Volume Up/Down message depending on host OS. */
static void usbDongle_LeaVolumeHandleUpdateUsbVolume(void)
{
    switch(GetTaskData()->host_os)
    {
        /* Send HID message until maximum count or target volume has been met. */
        case HOST_OS_TYPE_WINDOWS_MAC:
            if(GetTaskData()->usb_volume.count < USB_VOLUME_CHANGE_COUNT_MAX &&
               usbDongle_LeaCheckIfUsbVolumeUpDownCanBeApplied(GetTaskData()->usb_volume.target))
            {
                GetTaskData()->usb_volume.count++;
                DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeHandleUpdateUsbVolume, usb_volume.change_count %d",
                                  GetTaskData()->usb_volume.count );
                usbDongle_LeaVolumeSendUsbVolumeMessage();

                /* reschedule */
                MessageSendLater(GetTask(), VO_INTERNAL_UPDATE_USB_VOLUME, NULL, USB_DONGLE_VO_USB_VOLUME_CHANGE_DELAY_MS );
            }
            else
            {
                /* USB host has become unresponsive or volume up/down commands are not allowed to apply */
                DEBUG_LOG_ERROR("usbDongle_LeaVolumeHandleUpdateUsbVolume not sent either due to unresponsive host (count: %d) or marginal change",
                                 GetTaskData()->usb_volume.count);
                GetTaskData()->state = VO_INTERNAL_STATE_SINK_VOLUME_SYNC;
            }
            break;

        /* Send single HID message per volume change request. */
        case HOST_OS_TYPE_LINUX_ANDROID:
            usbDongle_LeaVolumeSendUsbVolumeMessage();
            break;

        /* Start a burst of HID messages to identify host OS. */
        case HOST_OS_TYPE_UNIDENTIFIED:
            if(GetTaskData()->usb_volume.count < BURST_SIZE)
            {
                GetTaskData()->usb_volume.count++;
                DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeHandleUpdateUsbVolume, "
                                  "usb_volume.change_count %d",GetTaskData()->usb_volume.count);
                usbDongle_LeaVolumeSendUsbVolumeMessage();
                MessageSendLater(GetTask(), VO_INTERNAL_UPDATE_USB_VOLUME, NULL, USB_DONGLE_VO_USB_VOLUME_CHANGE_DELAY_MS );
            }
            else
            {
                GetTaskData()->host_os = HOST_OS_TYPE_LINUX_ANDROID;
                DEBUG_LOG_INFO("usbDongle_LeaVolumeHandleUpdateUsbVolume, likely host OS: Linux or Android");
                GetTaskData()->state = VO_INTERNAL_STATE_SINK_VOLUME_SYNC;
            }
            break;

        default:
            break;
    }
}

static void usbDongle_LeaVolumeHandleUsbAudioConnectedInd(void)
{
    DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeHandleUsbAudioConnectedInd");
    GetTaskData()->usb_connected = TRUE;
    if(GetTaskData()->le_audio_connected)
    {
        GetTaskData()->state = VO_INTERNAL_STATE_SINK_VOLUME_SYNC;
    }
}

static void usbDongle_LeaVolumeHandleUsbAudioDisconnectedInd(void)
{
    DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeHandleUsbAudioDisconnectedInd");
    GetTaskData()->usb_connected = FALSE;
    GetTaskData()->state = VO_INTERNAL_STATE_IDLE;
    MessageCancelFirst(GetTask(), VO_INTERNAL_UPDATE_USB_VOLUME);
    MessageCancelFirst(GetTask(), VO_INTERNAL_UPDATE_LE_AUDIO_VOLUME);
}

/* if the USB mic status changed, we need to update LE Audio Client */
static void usbDongle_LeaVolumeHandleUsbMicMuteStatus(bool mic_muted)
{
    DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeHandleUsbMicMuteStatus, %d ", mic_muted);
    if(GetTaskData()->le_audio_vbc_active)
    {
        if(GetTaskData()->mic_muted != mic_muted)
        {
            //TODO: update le audio client
            GetTaskData()->mic_muted = mic_muted;
        }
    }
}

static void usbDongle_LeaVolumeHandleLeAudioConnected(LE_AUDIO_CLIENT_CONNECT_IND_T *msg)
{
    DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeHandleLeAudioConnected");

    if (msg->status == LE_AUDIO_CLIENT_STATUS_SUCCESS)
    {
        PanicFalse(GetTaskData()->le_audio_group_handle == 0);
        GetTaskData()->le_audio_group_handle = msg->group_handle;
    }
}

static void usbDongle_LeaVolumeHandleLeAudioDisconnected(LE_AUDIO_CLIENT_DISCONNECT_IND_T *msg)
{
    DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeHandleLeAudioDisconnected");

    UNUSED(msg);
    GetTaskData()->le_audio_group_handle = 0;
}

static void usbDongle_LeaVolumeHandleLeAudioStreamConnected(LE_AUDIO_CLIENT_STREAM_START_IND_T *msg)
{
    DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeHandleLeAudioStreamConnected");

    PanicFalse(GetTaskData()->le_audio_group_handle == msg->group_handle);

    if(msg->status != LE_AUDIO_CLIENT_STATUS_SUCCESS)
    {
        DEBUG_LOG_ERROR("usbDongle_LeaVolumeHandleLeAudioStreamConnected, unicast stream start failed");
        return;
    }

    if(msg->audio_context == CAP_CLIENT_CONTEXT_TYPE_GAME_WITH_VBC)
    {
        GetTaskData()->le_audio_vbc_active = TRUE;
    }

    /* Only one stream connection supported */
    if(msg->audio_context != CAP_CLIENT_CONTEXT_TYPE_PROHIBITED)
    {
        if(!GetTaskData()->le_audio_connected)
        {
            GetTaskData()->le_audio_connected = TRUE;
            if(GetTaskData()->usb_connected)
            {
                GetTaskData()->state = VO_INTERNAL_STATE_SINK_VOLUME_SYNC;
                usbDongle_LeaVolumeUpdateSinkVolume(AudioSources_GetVolume(audio_source_usb));
            }
        }
    }
}

static void usbDongle_LeaVolumeHandleLeAudioStreamDisconnected(LE_AUDIO_CLIENT_STREAM_STOP_IND_T *msg)
{
    DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeHandleLeAudioStreamDisconnected");

    UNUSED(msg);
    PanicFalse(GetTaskData()->le_audio_group_handle == msg->group_handle);
    GetTaskData()->le_audio_vbc_active = FALSE;

    /* Only one stream connection supported */
    if(GetTaskData()->le_audio_connected)
    {
        GetTaskData()->le_audio_connected = FALSE;
        MessageCancelFirst(GetTask(), VO_INTERNAL_UPDATE_USB_VOLUME);
        MessageCancelFirst(GetTask(), VO_INTERNAL_UPDATE_LE_AUDIO_VOLUME);
        GetTaskData()->state = VO_INTERNAL_STATE_IDLE;
    }

}

static void usbDongle_LeaVolumeHandleMessage(Task task, MessageId id, Message message)
{
    UNUSED(task);
    UNUSED(message);

    switch(id)
    {
        case VO_INTERNAL_UPDATE_USB_VOLUME:
            usbDongle_LeaVolumeHandleUpdateUsbVolume();
            break;

        case VO_INTERNAL_UPDATE_LE_AUDIO_VOLUME:
            usbDongle_LeaVolumeHandleUpdateLeAudioVolume();
            break;

        case USB_AUDIO_CONNECTED_IND:
            usbDongle_LeaVolumeHandleUsbAudioConnectedInd();
            break;

        case USB_AUDIO_DISCONNECTED_IND:
            usbDongle_LeaVolumeHandleUsbAudioDisconnectedInd();
            break;

        case USB_AUDIO_HEADSET_MIC_MUTE_ACTIVE:
            usbDongle_LeaVolumeHandleUsbMicMuteStatus(TRUE);
            break;

        case USB_AUDIO_HEADSET_MIC_MUTE_INACTIVE:
            usbDongle_LeaVolumeHandleUsbMicMuteStatus(FALSE);
            break;

        case LE_AUDIO_CLIENT_CONNECT_IND:
            usbDongle_LeaVolumeHandleLeAudioConnected((LE_AUDIO_CLIENT_CONNECT_IND_T *) message);
            break;

        case LE_AUDIO_CLIENT_DISCONNECT_IND:
            usbDongle_LeaVolumeHandleLeAudioDisconnected((LE_AUDIO_CLIENT_DISCONNECT_IND_T *) message);
            break;

        case LE_AUDIO_CLIENT_STREAM_START_IND:
            usbDongle_LeaVolumeHandleLeAudioStreamConnected((LE_AUDIO_CLIENT_STREAM_START_IND_T *) message);
            break;

        case LE_AUDIO_CLIENT_STREAM_STOP_IND:
            usbDongle_LeaVolumeHandleLeAudioStreamDisconnected((LE_AUDIO_CLIENT_STREAM_STOP_IND_T *) message);
            break;

        default:
            DEBUG_LOG_VERBOSE("usbDongle_LeaVolumeHandleMessage, unhandled %d", id);
            break;
    }
}


bool UsbDongle_LeaVolumeObserverInit(Task task)
{
    volume_t mid_volume;

    DEBUG_LOG_INFO("UsbDongle_VolumeObserverLeInit");
    UNUSED(task);

    GetTaskData()->task.handler = usbDongle_LeaVolumeHandleMessage;
    GetTaskData()->usb_connected = FALSE;
    GetTaskData()->le_audio_connected = FALSE;
    GetTaskData()->le_audio_vbc_active = FALSE;
    GetTaskData()->le_audio_group_handle = 0;
    GetTaskData()->state = VO_INTERNAL_STATE_IDLE;
    GetTaskData()->mic_muted = FALSE;
    GetTaskData()->host_os = HOST_OS_TYPE_UNIDENTIFIED;

    /* Old volume initialised to midpoint of USB volume range */
    GetTaskData()->old_volume.config = usb_config;
    GetTaskData()->old_volume.value = (usb_config.range.max - usb_config.range.min) / 2;;

    /* Mapping current host volume to the middle of the LE Audio volume range by
     * setting the LE Audio volume to middle of its range. */
    mid_volume.config = le_audio_config;
    mid_volume.value = (le_audio_config.range.max - le_audio_config.range.min) / 2;
    AudioSources_SetVolume(audio_source_le_audio_unicast, mid_volume);
    
    AudioSources_RegisterObserver(audio_source_usb, &usb_dongle_observer_interface);
    AudioSources_RegisterObserver(audio_source_le_audio_unicast, &usb_dongle_observer_interface);
    VoiceSources_RegisterObserver(voice_source_le_audio_unicast, &usb_dongle_voice_observer_interface);

    UsbAudio_ClientRegister(GetTask(), USB_AUDIO_REGISTERED_CLIENT_STATUS);
    LeAudioClient_ClientRegister(GetTask());

    return TRUE;
}
#endif /* INCLUDE_SOURCE_APP_LE_AUDIO */
